package ms.unicorn.language.languagedef;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ms.unicorn.Element;
import ms.unicorn.diagram.edit.parts.EntityEditPart;
import ms.unicorn.diagram.edit.parts.RelationEditPart;
import ms.unicorn.diagram.parsers.MessageFormatParser;
import ms.unicorn.diagram.part.UnicornDiagramEditorPlugin;
import ms.unicorn.modellanguage.LanguageElement;
import ms.unicorn.modellanguage.LanguageRegistry;
import ms.unicorn.ui.edit.parts.TextLabelEditPart;
import ms.unicorn.ui.diagram.styles.CollapsableConstraint;
import ms.unicorn.ui.diagram.styles.StylesFactory;
import ms.unicorn.util.ReflectionUtil;

import org.eclipse.draw2d.IFigure;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.gef.EditPart;
import org.eclipse.gmf.runtime.common.ui.services.parser.IParser;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.preferences.IPreferenceConstants;
import org.eclipse.gmf.runtime.draw2d.ui.figures.FigureUtilities;
import org.eclipse.gmf.runtime.notation.BasicCompartment;
import org.eclipse.gmf.runtime.notation.Connector;
import org.eclipse.gmf.runtime.notation.DecorationNode;
import org.eclipse.gmf.runtime.notation.FontStyle;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationFactory;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.RelativeBendpoints;
import org.eclipse.gmf.runtime.notation.Routing;
import org.eclipse.gmf.runtime.notation.Shape;
import org.eclipse.gmf.runtime.notation.Size;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.gmf.runtime.notation.datatype.RelativeBendpoint;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;

public class ViewDefUtil {
	
	private static interface IViewCreator {
		View resolveView(ViewDef viewDef, Element entity, 
				PreferencesHint preferencesHint, View parentView);
	}
	
	private static Map<String, IViewCreator> viewCreators;
	private static Map<String, IParser> parsers;
	
	static {
		viewCreators = new HashMap<String, ViewDefUtil.IViewCreator>();
		viewCreators.put("node", new NodeCreator());
		viewCreators.put("label", new LabelCreator());
		viewCreators.put("connector", new RelationCreator());
		viewCreators.put("compartment", new CompartmentCreator());
		
		parsers = new HashMap<String, IParser>();
	}
	
	public static void clearParserCache() {
		parsers.clear();
	}
	
	public static List<SubViewDef> getChildViews(ViewDef def) {
		return ReflectionUtil.getPropertyValue(def, "Children", null);
	}
	
	public static ViewDef getChildView(ViewDef def, String qualifiedName) {
		int idx = qualifiedName.indexOf(':');
		if (idx > 0) {
			ViewDef pdef = getChildView(def, qualifiedName.substring(0, idx));
			if (pdef != null)
				return getChildView(pdef, qualifiedName.substring(idx + 1));
		}
		else {
			List<SubViewDef> childViews = getChildViews(def);
			if (childViews != null)
				for (SubViewDef subdef: childViews)
					if (subdef.getName().equals(qualifiedName))
						return LanguageDefUtil.resolveDefinition(subdef.getView());
		}
		return null;
	}
	
	public static ViewDef resolveViewDef(View view) {
		if (view.getElement() instanceof Element) {
			LanguageElement le = LanguageRegistry.INSTANCE.getLanguageElement(((Element) view.getElement()).getElementType());
			if (le != null) {
				ViewDef def = LanguageDefUtil.resolveDefinition(le.getDefinition().getView());
				String subname = getQualifiedViewName(view);
				if (subname != null)
					def = getChildView(def, subname);
				return def;
			}
		}
		return null;
	}
	
	public static String getViewName(View view) {
		EAnnotation viewNameAnno = view.getEAnnotation("definition");
		if (viewNameAnno != null) 
			return viewNameAnno.getDetails().get("subview.name");
		return null;
	}
	
	public static String getQualifiedViewName(View view) {
		String name = getViewName(view);
		if (name != null && view.eContainer() instanceof View) {
			String pName = getQualifiedViewName((View)view.eContainer());
			if (pName != null)
				name = pName + ':' + name;
		}
		return name;
	}
	
	public static void setViewName(View view, String name) {
		EAnnotation viewNameAnno = view.getEAnnotation("definition");
		if (viewNameAnno == null) {
			viewNameAnno = EcoreFactory.eINSTANCE.createEAnnotation();
			viewNameAnno.setSource("definition");
			view.getEAnnotations().add(viewNameAnno);
		}
		viewNameAnno.getDetails().put("subview.name", name);
	}
		
	@SuppressWarnings("unchecked")
	public static Class<? extends EditPart> getViewEditPart(ViewDef view) {
		Class<? extends EditPart> clazz = null;
		String clazzName = view.getEditpart();
		
		if (clazzName == null || clazzName.length() == 0) {
			if (view instanceof NodeViewDef)
				clazz = EntityEditPart.class;
			else if (view instanceof RelationViewDef)
				clazz = RelationEditPart.class;
			else if (view instanceof LabelViewDef)
				clazz = TextLabelEditPart.class;
			else if (view instanceof CompartmentViewDef)
				clazz = null;
		}
		else clazz = (Class<? extends EditPart>) ReflectionUtil.getClass(clazzName);
		return clazz;
	}
	
	public static String getName(EObject obj) {
		Method getNameMed = ReflectionUtil.getMethod(obj.getClass(), "getName");
		String name = (String) ReflectionUtil.invoke(getNameMed, obj);
		if (name == null || name.length() == 0)
			name = Integer.toHexString(obj.hashCode());
		return name;
	}
	
	public static String getQualifiedName(EObject obj) {
		return (obj.eContainer() != null ? getQualifiedName(obj.eContainer()) + "." : "")
				+ getName(obj);
 	}
	
	public static IParser getParser(LabelViewDef viewDef) {
		String name = getQualifiedName(viewDef);
		
		MessageFormatParser parser = (MessageFormatParser) parsers.get(name);
		if (parser == null) {
			List<EAttribute> viewFeatures = new ArrayList<EAttribute>();
			for(LabelSourceField field: viewDef.getViewFields()) {
				EAttribute attr = TagDefUtil.getEAttribute(field); 
				if (attr != null) viewFeatures.add(attr);
			}
			
			List<EAttribute> editFeatures = new ArrayList<EAttribute>();
			for(LabelSourceField field: viewDef.getEditFields()) {
				EAttribute attr = TagDefUtil.getEAttribute(field); 
				if (attr != null) editFeatures.add(attr);
			}
			
			EAttribute[] features = viewFeatures.toArray(new EAttribute[viewFeatures.size()]);
			EAttribute[] editableFeatures = 
					editFeatures.size() > 0 ? editFeatures.toArray(new EAttribute[editFeatures.size()]):
						features;
			
			String viewPattern = viewDef.getViewPattern();
			String editPattern = viewDef.getEditPattern();
			if (editPattern == null || editPattern.length() == 0)
				editPattern = viewPattern;
					
			parser = new MessageFormatParser(features,	editableFeatures);
			parser.setViewPattern(viewPattern);
			parser.setEditorPattern(editPattern);
			parser.setEditPattern(editPattern);		
			
			parsers.put(name, parser);
		}
		return parser;
	}
		
	public static View resolveView(ViewDef viewDef, Element entity, PreferencesHint preferencesHint, View parentView) {
		View view = null;
		
		IViewCreator creator = viewCreators.get(viewDef.getViewType());
		if (creator != null)
			view = creator.resolveView(viewDef, entity, preferencesHint, parentView);
		
		if (view != null && (viewDef instanceof NodeViewDef || viewDef instanceof RelationViewDef)) { //			
			// create an entry in editpartMap
			List<SubViewDef> children = ReflectionUtil.getPropertyValue(viewDef, "Children", null);
			if (children != null)
				for(SubViewDef subViewDef: children) {
					ViewDef subViewDefView = LanguageDefUtil.resolveDefinition(subViewDef.getView());
					View subview = resolveView(subViewDefView, entity, preferencesHint, view);
					setViewName(subview, subViewDef.getName());
					//subview.setName(subViewDef.getName());
					//subview.setAssociateSubViewDef(subViewDef);
					ViewUtil.insertChildView(view, subview, ViewUtil.APPEND, true);
					if (viewDef instanceof RelationViewDef && subview instanceof Node) {
						Node child = (Node) subview;
						child.setLayoutConstraint(NotationFactory.eINSTANCE.createLocation());
						org.eclipse.gmf.runtime.notation.Location location = (org.eclipse.gmf.runtime.notation.Location)child.getLayoutConstraint();
						location.setX(0);
						location.setY(5);
					}
				}
		}
		return view;
	}
	
	public static IFigure getViewFigure(String figureName, FigureContainer figures) {
		return figures.getFigure(figureName);
	}
	
	protected static class NodeCreator implements IViewCreator {

		@Override
		public View resolveView(ViewDef viewDef, Element entity, PreferencesHint preferencesHint, View parentView) {

			Shape node = NotationFactory.eINSTANCE.createShape();
			//node.setDefinition(viewDef);
			
			node.setLayoutConstraint(NotationFactory.eINSTANCE.createBounds());
					
			//node.setType("ms.unicorn.Entity");
			
			node.setElement(entity);
			//ViewUtil.stampShortcut(containerView, node);
			
			// initializeFromPreferences 
			if (preferencesHint == null)
				preferencesHint = UnicornDiagramEditorPlugin.DIAGRAM_PREFERENCES_HINT;
			
			final IPreferenceStore prefStore = (IPreferenceStore) preferencesHint
					.getPreferenceStore();
			
			
		
			RGB lineRGB = PreferenceConverter.getColor(prefStore, IPreferenceConstants.PREF_LINE_COLOR);
			ViewUtil.setStructuralFeatureValue(node,
					NotationPackage.eINSTANCE.getLineStyle_LineColor(),
					FigureUtilities.RGBToInteger(lineRGB));
			FontStyle nodeFontStyle = (FontStyle) node
					.getStyle(NotationPackage.Literals.FONT_STYLE);
			if (nodeFontStyle != null) {
				FontData fontData = PreferenceConverter.getFontData(prefStore,
						IPreferenceConstants.PREF_DEFAULT_FONT);
				nodeFontStyle.setFontName(fontData.getName());
				nodeFontStyle.setFontHeight(fontData.getHeight());
				nodeFontStyle.setBold((fontData.getStyle() & SWT.BOLD) != 0);
				nodeFontStyle.setItalic((fontData.getStyle() & SWT.ITALIC) != 0);
				RGB fontRGB = PreferenceConverter
						.getColor(prefStore, IPreferenceConstants.PREF_FONT_COLOR);
				nodeFontStyle.setFontColor(FigureUtilities.RGBToInteger(fontRGB)
						.intValue());
			}
			RGB fillRGB = PreferenceConverter.getColor(
					prefStore, IPreferenceConstants.PREF_FILL_COLOR);
			ViewUtil.setStructuralFeatureValue(node,
					NotationPackage.eINSTANCE.getFillStyle_FillColor(),
					FigureUtilities.RGBToInteger(fillRGB));
			
			return node;
		}
		
	}

	protected static class RelationCreator implements IViewCreator {

		@SuppressWarnings("unchecked")
		@Override
		public View resolveView(ViewDef viewDef, Element relation, PreferencesHint preferencesHint, View parentView) {
			Connector edge = NotationFactory.eINSTANCE.createConnector();
			//edge.setDefinition(viewDef);
			
			edge.getStyles().add(NotationFactory.eINSTANCE.createFontStyle());
			
			RelativeBendpoints bendpoints = NotationFactory.eINSTANCE
					.createRelativeBendpoints();
			ArrayList<RelativeBendpoint> points = new ArrayList<RelativeBendpoint>(2);
			points.add(new RelativeBendpoint());
			points.add(new RelativeBendpoint());
			bendpoints.setPoints(points);
			edge.setBendpoints(bendpoints);
			
			edge.setElement(relation);

			if (preferencesHint == null)
				preferencesHint = UnicornDiagramEditorPlugin.DIAGRAM_PREFERENCES_HINT;
			
			// initializePreferences
			final IPreferenceStore prefStore = (IPreferenceStore) preferencesHint
					.getPreferenceStore();
		
			org.eclipse.swt.graphics.RGB lineRGB = PreferenceConverter.getColor(
					prefStore, IPreferenceConstants.PREF_LINE_COLOR);
			ViewUtil.setStructuralFeatureValue(edge,
					NotationPackage.eINSTANCE.getLineStyle_LineColor(),
					FigureUtilities.RGBToInteger(lineRGB));
			FontStyle edgeFontStyle = (FontStyle) edge
					.getStyle(NotationPackage.Literals.FONT_STYLE);
			if (edgeFontStyle != null) {
				FontData fontData = PreferenceConverter.getFontData(prefStore,
						IPreferenceConstants.PREF_DEFAULT_FONT);
				edgeFontStyle.setFontName(fontData.getName());
				edgeFontStyle.setFontHeight(fontData.getHeight());
				edgeFontStyle.setBold((fontData.getStyle() & SWT.BOLD) != 0);
				edgeFontStyle.setItalic((fontData.getStyle() & SWT.ITALIC) != 0);
				org.eclipse.swt.graphics.RGB fontRGB = PreferenceConverter
						.getColor(prefStore, IPreferenceConstants.PREF_FONT_COLOR);
				edgeFontStyle.setFontColor(FigureUtilities.RGBToInteger(fontRGB)
						.intValue());
			}
			Routing routing = Routing.get(prefStore
					.getInt(IPreferenceConstants.PREF_LINE_STYLE));
			if (routing != null) {
				ViewUtil.setStructuralFeatureValue(edge,
						NotationPackage.eINSTANCE.getRoutingStyle_Routing(),
						routing);
			}
			return edge;
			/*Node label6001 = ViewUtil.createLabel(edge,
							UnicornVisualIDRegistry
							.getType(RelationElementTypeEditPart.VISUAL_ID));
			label6001.setLayoutConstraint(NotationFactory.eINSTANCE
					.createLocation());
			Location location6001 = (Location) label6001.getLayoutConstraint();
			location6001.setX(0);
			location6001.setY(40);*/

		}
		
	}
	
	protected static class LabelCreator implements IViewCreator {

		@Override
		public View resolveView(ViewDef viewDef, Element entity,
				PreferencesHint preferencesHint, View parentView) {
			 DecorationNode label =  NotationFactory.eINSTANCE.createDecorationNode(); //new DecorationNodeEx();
			 //label.setDefinition(viewDef);
			 return label;
		}
		
	}
	
	protected static class CompartmentCreator implements IViewCreator {

		@SuppressWarnings("unchecked")
		@Override
		public View resolveView(ViewDef viewDef, Element entity, PreferencesHint preferencesHint, View parentView) {
			CollapsableConstraint sizes = StylesFactory.eINSTANCE.createCollapsableConstraint();
			//Dimension size = ERML.SubPart.getDefaultSize();
			Size collapseSize = NotationFactory.eINSTANCE.createSize();
			collapseSize.setHeight(60);
			collapseSize.setWidth(40);
			sizes.setCollapsedSize(collapseSize);
			
			Size expandSize = NotationFactory.eINSTANCE.createSize();
			expandSize.setHeight(300);
			expandSize.setWidth(300);
			sizes.setExpandedSize(expandSize);

			parentView.getStyles().add(sizes);

			BasicCompartment compartment = NotationFactory.eINSTANCE.createBasicCompartment();
			compartment.setCollapsed(true);
			return compartment;
		}
		
	}

	
}
